﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MCCA.Cache
{
    public class CacheEntityTable<K, V>
    {
        #region [           Members           ]

        readonly string _cacheKey;

        ICacheEngine engine;

        readonly TimeSpan duration;

        #endregion

        #region [             Ctor.           ]

        internal CacheEntityTable(string cacheKey, ICacheEngine engine, int cacheSeconds = 0)
        {
            this._cacheKey = cacheKey;
            this.engine = engine;

            duration = TimeSpan.FromSeconds(cacheSeconds);
        }

        #endregion

        #region [             Cache           ]

        private string GetCacheKey(K key)
        {
            return string.Format("{0}-->{1}", _cacheKey, key);
        }

        public bool Cache(K key, V entity)
        {
            return engine.Store(GetCacheKey(key), entity, duration);
        }

        public V Get(K key)
        {
            return engine.Get<V>(GetCacheKey(key));
        }

        public Dictionary<K, V> Get(IEnumerable<K> keys)
        {
            Dictionary<K, V> result = new Dictionary<K, V>();

            var dic = engine.Get(keys.Select(k => GetCacheKey(k)));

            foreach (var key in keys)
            {
                string k = GetCacheKey(key);

                if (dic.ContainsKey(k))
                    result.Add(key, (V)dic[GetCacheKey(key)]);
            }

            return result;
        }

        public bool TryGetValue(K key, out V entity)
        {
            object obj = null;
            entity = default(V);

            if (!engine.TryGet(GetCacheKey(key), out obj))
                return false;
            else
            {
                entity = (V)obj;
                return true;
            }
        }


        public bool Remove(K key)
        {
            return engine.Remove(GetCacheKey(key));
        }


        #endregion

        #region [           DB Proxy          ]


        public V GetProxy(K key, Func<K, V> getFromDB)
        {
            V entity = default(V);

            // if we found a cache item
            if (TryGetValue(key, out entity))
            {
                return entity;
            }

            entity = getFromDB(key);
            Cache(key, entity);

            return entity;
        }

        public List<V> GetProxy(IEnumerable<K> keys, Func<IEnumerable<K>, Dictionary<K, V>> getFromDB)
        {
            List<K> keysToDB = new List<K>();

            Dictionary<K, V> dic = new Dictionary<K, V>();

            var exist = Get(keys); //try get by keys from cache

            foreach (var key in keys) //build the result set.
            {
                if (exist.ContainsKey(key))
                    dic.Add(key, exist[key]);
                else
                {
                    keysToDB.Add(key);
                    dic.Add(key, default(V));
                }
            }

            if (keysToDB.Count == 0)
                return dic.Values.ToList();


            Dictionary<K, V> fromdb = getFromDB(keysToDB); //get lasts from cache

            //save to cache.
            Task.Run(() =>
            {
                foreach (var item in fromdb)
                {
                    Cache(item.Key, item.Value);
                }
            });

            foreach (var item in fromdb)
            {
                dic[item.Key] = item.Value;
            }

            return dic.Values.ToList();
        }


        #endregion
    }
}
